package mpp;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Ex3Q1 {
	static int counter = 0;
	private static  Lock _lock = null;
	
	public static void main(String[] args)
	{
		final int NUM_OF_THREADS = 2;
		final int ITERATIONS = 500000;
		
		
		
		if (args.length != 1) {
			System.out.println("Wrong number of arguments.");
			return;
		}
		
		
		if (args[0].toLowerCase().equals("peterson"))	_lock = new PetersonLock(); 
		else if (args[0].toLowerCase().equals("caslock"))	_lock = new CASLock();
		else {
			System.out.println("Use \"Peterson\" or \"CASLock\" as argument.");
			return;
		}
		

		Thread[] threads = new Thread[NUM_OF_THREADS];
		long start = System.currentTimeMillis();
		
		//Initialize threads
		for (int i = 0; i < NUM_OF_THREADS; i++)
		{
			threads[i] = new Thread(){
				
				
				
				public void run()
				{
					for (int i = 0; i < ITERATIONS; i++)
					{
						_lock.lock();
						try
						{
							int temp = counter;
							temp++;
							counter = temp;
						} finally
						{
							_lock.unlock();
						}
					}
				}
			};
		}
		
		//start the threads
		for (int i = 0; i < NUM_OF_THREADS; i++)
		{
			threads[i].start();
		}

		//waiting for the threads to end
		try
		{
			for (int i = 0; i < NUM_OF_THREADS; i++)
			{
				threads[i].join();
			}
		} catch (InterruptedException e)
		{
		}

		System.out.format("Finished after %d miliseconds. The couner is: %d\n",
				new Long((System.currentTimeMillis() - start)), counter);
	}

}

@SuppressWarnings("serial")
class CASLock extends ReentrantLock{
	
	volatile AtomicInteger value = new AtomicInteger(-1);
	
	
	@Override
	public void lock() {
		int threadID = ThreadID.get();
		while(!value.compareAndSet(-1, threadID))	{};
	}
	
	@Override
	public void unlock() {
		value.set(-1);
	}
	
}

@SuppressWarnings("serial")
class PetersonLock extends ReentrantLock{
	private volatile boolean[] flag = new boolean[2];
	private volatile int victim = -1;
	
	@Override
	public void lock() {
		int i = ThreadID.get();
		int j = 1-i;
		flag[i] = true; 
		 victim  = i; 
		 while (flag[j] && victim == i) {};		
	}

	@Override
	public void unlock() {
		int i = ThreadID.get();
		flag[i] = false;			
	}

}


class ThreadID2 {
	private static volatile int nextID = 0;
	private static class ThreadLocalID extends ThreadLocal<Integer> {
	
		protected synchronized Integer initialValue() {
			return nextID++;
			}
		}
	
	 private static ThreadLocalID threadID = new ThreadLocalID();
	 
	 public static int get() {
		 return threadID.get();
		 }
	 
	 public static void set(int index) {
		 threadID.set(index);
		 }
}


class Ex3Q2{
	static MyQueue queue = new MyQueue();
	
	public static void main(String[] args)
	{
		final int ITERATIONS = 100000;
		int NUM_OF_THREADS;
		
		
		if (args.length != 2) {
			System.out.println("Wrong number of arguments.");
			return;
		}
		
		
		if (args[0].toLowerCase().equals("onecaslockqueue")){	
			queue._enqLock = new CASLock();
			queue._deqLock = queue._enqLock;
		}
		else if (args[0].toLowerCase().equals("twocaslockqueue")){	
			queue._enqLock = new CASLock();
			queue._deqLock = new CASLock();
		}
		else {
			System.out.println("Use \"OneCasLockQueue\" or \"TwoCasLockQueue\" as first argument.");
			return;
		}
		

		try{
			NUM_OF_THREADS = Integer.parseInt(args[1]);
			if (NUM_OF_THREADS<1) throw new Exception("Error: Expecting a positive number and not negaitve or zero.");
		} catch (Exception ex) {
			System.out.println("2nd Argument is not a positive number.");
			return;
		}
		
		
		Thread[] threads = new Thread[NUM_OF_THREADS];
		long start = System.currentTimeMillis();
		
		//Initialize threads
		for (int i = 0; i < NUM_OF_THREADS; i++)
		{
			threads[i] = new Thread(){
				public void run()
				{
					for (int i = 1; i <= ITERATIONS; i++)
					{
							if (i%2 == 0) queue.deq();
							else queue.enq(i);
					}
				}
			};
		}
		
		//start the threads
		for (int i = 0; i < NUM_OF_THREADS; i++)
		{
			threads[i].start();
		}

		//waiting for the threads to end
		try
		{
			for (int i = 0; i < NUM_OF_THREADS; i++)
			{
				threads[i].join();
			}
		} catch (InterruptedException e)
		{
		}

		System.out.format("Finished after %d miliseconds.\n",
				new Long((System.currentTimeMillis() - start)));
		
		
	}

	
}


class MyQueue{
	
	public Lock _enqLock;
	public Lock _deqLock;
	
	private final int QSIZE = 8;
	private int head = 0, tail = 0;
	int items[] = new int[QSIZE];
	
	public void enq(int x) {
		while (tail-head == QSIZE);
			//System.out.println("Thread " + ThreadID.get() + " encountered a full queue");
		
		// Locking the enqueue lock
		_enqLock.lock();
		try{
			items[tail % QSIZE] = x; 
			tail++;
		} finally {
			
			//Unlocking the enqueue lock
			_enqLock.unlock();
		}
	}
	
	public int deq() {
		int item;
		
		while (tail == head);
			//System.out.println("Thread " + ThreadID.get() + " encountered an empty queue.");
		
		//Locking the Dequeue Lock
		_deqLock.lock();
		try
		{
			
		
		 item = items[head % QSIZE]; 
		
		head++;
		} finally {
		
			// Unlocking the dequeue lock
			_deqLock.unlock();
		}
		return item;
	}
	
	public boolean isEmpty(){
		return tail==head;
	}
}


